package redis

import (
	"context"
	"fmt"
	"time"
	"yunj/pkg/global"

	"github.com/google/uuid"
)

// Lock 定义 Redis 分布式锁结构体
type Lock struct {
	key     string
	value   string
	ttl     time.Duration
	isWait  bool      // 是否等待锁
	getTime time.Time // 获取锁的时间
	ctx     context.Context
	cancel  context.CancelFunc
}

// 创建一个新的 Redis 分布式锁实例
// ttl  锁的过期时间
// isWait  是否需要等待
func NewLock(key string, ttl time.Duration, isWait ...bool) *Lock {
	l := &Lock{
		key:   FullKey(key),
		value: uuid.New().String(), // 生成唯一的锁值
		ttl:   ttl,
	}
	// 如果指定了等待时间，则使用指定的等待时间
	if len(isWait) > 0 {
		l.isWait = isWait[0]
	}
	return l
}

// 获取锁
func (l *Lock) Get() (res bool, err error) {
	// 不需要等待
	if !l.isWait {
		res, err = l.get()
		return
	}
	// 需要等待
	beginTime := time.Now()
	// 等待时间（需大于锁过期时间，避免并发引起的获取顺序冲突）
	waitTime := l.ttl * 2
	for {
		if time.Now().After(beginTime.Add(waitTime)) {
			// 锁获取超时
			err = fmt.Errorf("lock timeout")
			return
		}
		res, err = l.get()
		if err != nil || res {
			return
		}
		time.Sleep(100 * time.Millisecond) // 等待100毫秒，避免频繁请求
	}
}

// 获取锁
func (l *Lock) get() (res bool, err error) {
	l.ctx = context.Background()
	set, err := global.Redis.SetNX(l.ctx, l.key, l.value, l.ttl).Result()
	// fmt.Printf("111|%s|%v|%v\r\n", l.key, set, err)
	if err != nil {
		return
	}
	if set {
		l.ctx, l.cancel = context.WithCancel(l.ctx)
		go l.renew()
		res = true
		l.getTime = time.Now()
		return
	}
	return
}

// 锁续约
func (l *Lock) renew() {
	// 每隔 锁过期时间的一半 触发一次续约操作
	ticker := time.NewTicker(l.ttl / 2)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			script := `
						if redis.call("get", KEYS[1]) == ARGV[1] then
							return redis.call("expire", KEYS[1], ARGV[2])
						else
							return 0
						end
						`
			result, err := global.Redis.Eval(l.ctx, script, []string{l.key}, l.value, int(l.ttl.Seconds())).Result()
			if err != nil || result.(int64) == 0 {
				// 续约失败，可能锁已被释放
				l.cancel()
				return
			}
		case <-l.ctx.Done():
			return
		}
	}
}

// 释放锁
// 需要结合 Get() 方法使用，不然l.ctx=nil
func (l *Lock) Release() (res bool, err error) {
	defer func() {
		if l.cancel != nil {
			l.cancel()
		}
	}()

	script := `
				redis.log(redis.LOG_DEBUG, "KEYS[1]: " .. KEYS[1])
				if redis.call("get", KEYS[1]) == ARGV[1] then
					return redis.call("del", KEYS[1])
				else
					return 0
				end
				`
	result, err := global.Redis.Eval(l.ctx, script, []string{l.key}, l.value).Result()
	if err != nil {
		err = fmt.Errorf("%s锁释放失败： %v", l.key, err)
		return
	}
	res = result.(int64) >= 1
	return
}

// 清理锁
// 之间尝试删除锁，如果删除失败，则不做任何处理
func (l *Lock) Clear() (err error) {
	if l.cancel != nil {
		l.cancel()
	}
	ctx := context.Background()
	_, err = global.Redis.Del(ctx, l.key).Result()
	if err != nil {
		err = fmt.Errorf("redis锁 %s 清理失败！%v", l.key, err)
	}
	return
}
